/*
 * TaskFactory.cpp
 *
 *  Created on: 10.04.2010
 *      Author: artur
 */

#include <hw/Platform.h>
#include <mem/page/PageFactory.h>
#include <mem/MemTypes.h>
#include <sys/sched/task/TaskFactory.h>

extern kernel::Console cout;
extern kernel::Platform hwplatform;
extern kernel::sys::sched::task::Task *current;

namespace kernel
{
  namespace sys
  {
    namespace sched
    {
      namespace task
      {

        /**
         *
         */
        TaskFactory::TaskFactory()
        {
          OUTDEBUG("[ TaskFactory ]");
          pFactory = 0;
        }

        /**
         *
         */
        TaskFactory::~TaskFactory()
        {
          OUTDEBUG("[ TaskFactory ]");
        }

        /**
         *
         */
        Task *
        TaskFactory::newTask(kernel::sys::sched::task::Task::task_entry_t e,
            kernel::sys::sched::task::Task::LEVEL l)
        {
          OUTDEBUG("[ TaskFactory ]");
          Task *tsk = new Task(e, l);
          if (pFactory)
            tsk->vmap.setPgd(
                (pgd_t) pFactory->alloc((size_t)(PAGE_SIZE * 4), 2)); // 16KB alignment

          tsk->vmap = kmap;
          // tsk->vmap += kernel::mem::MemArea(0x10000000, 0x10000000);
          ::hwplatform.bootCpu._task_init(tsk);
          return tsk;
        }

        /**
         *
         */
        Task *
        TaskFactory::newTask(kernel::sys::sched::task::Task::LEVEL _l)
        {
          OUTDEBUG("[ TaskFactory ]");
          Task *tsk = new Task(0, _l);
          if (pFactory)
            tsk->vmap.setPgd(
                (pgd_t) pFactory->alloc((size_t)(PAGE_SIZE * 4), 2)); // 16KB alignment
          tsk->vmap = kmap;
          // TODO Strange MemArea(), just for VmMap::getFreeVAddr()
          // tsk->vmap += kernel::mem::MemArea(0xb0000000, 0x10000000);
          // ::hwplatform.bootCpu._task_init(tsk);
          return tsk;
        }

        /**
         *
         */
        TaskFactory &
        TaskFactory::operator <<(kernel::mem::page::PageFactory &p)
        {
          OUTDEBUG("[ TaskFactory ]");
          pFactory = &p;
          return *this;
        }

        /**
         *
         */
        TaskFactory &
        TaskFactory::operator <<(kernel::cmd::CMD mode)
        {
          OUTDEBUG("[ TaskFactory ]");
          switch (mode)
            {
          case kernel::cmd::INIT:
            init();
            break;
          default:
            break;
            }
          return *this;
        }

        /**
         *
         */
        void
        TaskFactory::init(void)
        {
          kmap.setPgd((pgd_t) get_cr3());
          OUTDEBUG("[ TaskFactory ]");
          ::cout << "KMAP PGD -----> " << (int) kmap.pgd << "\n";
        }

        /**
         * \brief
         *
         * Task create sequence:
         * Task : TaskFactory >> LoaderFactory >> MemFactory >> Cpu >> Scheduler.
         */
        kernel::sys::sched::task::Task *
        TaskFactory::taskFromMemory(vaddr_t ptr)
        {
          int i;
          Elf32_Ehdr *e_hdr;
          Elf32_Phdr *p_hdr;
          Elf32_Shdr *s_hdr;
          vaddr_t strtab;

          //					OUTDEBUG("[ TaskFactory ]");

          e_hdr = (Elf32_Ehdr *) ptr;
          p_hdr = (Elf32_Phdr *) ((vaddr_t) e_hdr + e_hdr->e_phoff);

          kernel::sys::sched::task::Task *utsk = this->newTask(
              kernel::sys::sched::task::Task::USER);

          /* get shstrtab address */
          if (e_hdr->e_shstrndx)
            {
              s_hdr = (Elf32_Shdr *) ((vaddr_t) e_hdr + e_hdr->e_shoff);
              s_hdr += e_hdr->e_shstrndx;
              strtab = (vaddr_t) e_hdr + s_hdr->sh_offset;
            }

          /* allocating section memory */
          s_hdr = (Elf32_Shdr *) ((vaddr_t) e_hdr + e_hdr->e_shoff);

          for (i = 0; i < (int) e_hdr->e_shnum; i++, s_hdr++)
            {
              if (s_hdr->sh_addr)
                {
                  if (s_hdr->sh_size > 0)
                    {
                      ::cout << kernel::Console::HEX;
                      ::cout << "new MemArea(" << (int) s_hdr->sh_addr << ","
                          << (int) s_hdr->sh_size << ")\n";
                      utsk->vmap += kernel::mem::MemArea(s_hdr->sh_addr,
                          s_hdr->sh_size);
                      ::cout << kernel::Console::DEC;
                    }
                }
            }

#warning "This is architecture dependent code!!!! Move this to right place!!!"
          /* Add stack memory area */
          utsk->vmap += kernel::mem::MemArea(0xa0000000, 2 * TASK_STACK_SIZE);
          utsk->tsk_context.u_stack_top = 0xa0000000 + 2 * TASK_STACK_SIZE;
          // FIXME dummy align (16byte)
          utsk->tsk_context.u_stack_top &= ~(0x10 - 1);

          /* Normalize memory */
          utsk->vmap.normalize();
          utsk->entry = (Task::task_entry_t) e_hdr->e_entry;
          /* prepare virtual memory map */
          utsk->vmap.doMapping();

          /* lock scheduler */
          *::hwplatform.scheduler << ::kernel::cmd::PAUSE;

          /* Now copy sections into memory */
          /* first switch mmu for new task */
          ::hwplatform._switch_mmu((utsk->vmap.pgd));

          s_hdr = (Elf32_Shdr *) ((vaddr_t) e_hdr + e_hdr->e_shoff);

          for (i = 0; i < (int) e_hdr->e_shnum; i++, s_hdr++)
            {
              if (s_hdr->sh_addr && s_hdr->sh_size > 0)
                {
                  /* for NOBITS sections */
                  if (s_hdr->sh_type == SHT_NOBITS)
                    {
                      continue;
                    }
                  else
                    {
                      ::cout << ::kernel::Console::HEX << "COPYING to 0x"
                          << (int) s_hdr->sh_addr << " from 0x" << (int) (ptr
                          + s_hdr->sh_offset) << ", siz=0x"
                          << (int) s_hdr->sh_size << "\n";
                      ::cout << ::kernel::Console::DEC;
                      ::util::memcpy((void *) s_hdr->sh_addr,
                          (unsigned char *) (ptr + s_hdr->sh_offset),
                          (size_t) s_hdr->sh_size);
                    }
                }
            }

          /* initialize newly created task */
          ::hwplatform.bootCpu._task_init(utsk);

          /* switch pgd back */
          ::hwplatform._switch_mmu((current->vmap.pgd));

          /* unlock scheduler */
          *::hwplatform.scheduler << ::kernel::cmd::RESUME;

          return utsk;
        }

      } // task
    } // sched
  } // sys
} // kernel
